﻿using System.Collections;
using System.Data;
using System.IO;
using System.Text;

namespace SpectrumAnalyser.Common.Csv
{
    public class CsvReader
    {
        private readonly char _delimeter = ' ';
        private readonly FileInfo _csvFileInfo;
        private readonly bool _readHeaders;
        private readonly bool _skipEmptyLines;
        private readonly bool _breakOnEmptyLines;

        public CsvReader(string filePath, bool readHeders = true, bool skipEmprtyLines = false, bool breakOnEmptyLine = false)
        {
            _csvFileInfo = new FileInfo(filePath);
            _readHeaders = readHeders;
            _skipEmptyLines = skipEmprtyLines;
            _breakOnEmptyLines = breakOnEmptyLine;
        }

        public CsvReader(string filePath, char delimeter, bool readHeders = true, bool skipEmprtyLines = false, bool breakOnEmptyLine = false)
        {
            _csvFileInfo = new FileInfo(filePath);
            _delimeter = delimeter;
            _readHeaders = readHeders;
            _skipEmptyLines = skipEmprtyLines;
            _breakOnEmptyLines = breakOnEmptyLine;
        }

        public static DataTable ReadTable(string filePath, char delimeter, bool readHeders = true, bool skipEmprtyLines = false, bool breakOnEmptyLine = false)
        {
            var reader = new CsvReader(filePath, delimeter, readHeders, skipEmprtyLines, breakOnEmptyLine);
            return reader.ReadTable();
        }

        public DataTable ReadTable()
        {
            var table = new DataTable();
            if (!_csvFileInfo.Exists)
            {
                return table;
            }

            ArrayList alResult;
            using (var streamReader = new StreamReader(_csvFileInfo.FullName))
            {
                string strLineText;
                int counter = 0;
                while ((strLineText = streamReader.ReadLine()) != null)
                {
                    alResult = CSVParser(strLineText);
                    bool isEmptyLine = alResult.Count == 1 && string.IsNullOrEmpty(alResult[0].ToString());
                    if (_breakOnEmptyLines && isEmptyLine)
                    {
                        break;
                    }
                    if (_skipEmptyLines && isEmptyLine)
                    {
                        continue;
                    }

                    if (counter > 0 || (counter > 0 && !_readHeaders))
                    {
                        var array = new object[alResult.Count];
                        for (int i = 0; i < alResult.Count; i++)
                        {
                            array[i] = alResult[i];
                        }

                        table.Rows.Add(array);
                    }
                    else
                    {
                        foreach (object columnResult in alResult)
                        {
                            table.Columns.Add(columnResult.ToString());
                        }
                    }
                    counter++;
                }
            }

            return table;
            return table;
        }

        private ArrayList CSVParser(string strInputString)
        {
            int intCounter = 0, intLenght;
            StringBuilder strElem = new StringBuilder();
            ArrayList alParsedCsv = new ArrayList();
            intLenght = strInputString.Length;
            strElem = strElem.Append("");
            int intCurrState = 0;
            int[][] aActionDecider = new int[9][];

            //Build the state array
            aActionDecider[0] = new int[4] {2, 0, 1, 5};
            aActionDecider[1] = new int[4] {6, 0, 1, 5};
            aActionDecider[2] = new int[4] {4, 3, 3, 6};
            aActionDecider[3] = new int[4] {4, 3, 3, 6};
            aActionDecider[4] = new int[4] {2, 8, 6, 7};
            aActionDecider[5] = new int[4] {5, 5, 5, 5};
            aActionDecider[6] = new int[4] {6, 6, 6, 6};
            aActionDecider[7] = new int[4] {5, 5, 5, 5};
            aActionDecider[8] = new int[4] {0, 0, 0, 0};

            for (intCounter = 0; intCounter < intLenght; intCounter++)
            {
                intCurrState = aActionDecider[intCurrState][GetInputID(strInputString[intCounter])];
                //take the necessary action depending upon the state returned
                PerformAction(ref intCurrState, strInputString[intCounter], ref strElem, ref alParsedCsv);
            }
            intCurrState = aActionDecider[intCurrState][3];
            PerformAction(ref intCurrState, '\0', ref strElem, ref alParsedCsv);
            return alParsedCsv;
        }

        private int GetInputID(char chrInput)
        {
            if (chrInput == '"')
            {
                return 0;
            }
            if (chrInput == _delimeter)
            {
                return 1;
            }

            return 2;
        }

        private void PerformAction(ref int intCurrState, char chrInputChar, ref StringBuilder strElem,
                                          ref ArrayList alParsedCsv)
        {
            string strTemp = null;
            switch (intCurrState)
            {
                case 0:
                    //Seperate out value to array list
                    strTemp = strElem.ToString();
                    alParsedCsv.Add(strTemp);
                    strElem = new StringBuilder();
                    break;
                case 1:
                case 3:
                case 4:
                    //accumulate the character
                    strElem.Append(chrInputChar);
                    break;
                case 5:
                    //End of line reached. Seperate out value to array list
                    strTemp = strElem.ToString();
                    alParsedCsv.Add(strTemp);
                    break;
                case 6:
                    //Erroneous input. Reject line.
                    alParsedCsv.Clear();
                    break;
                case 7:
                    //wipe ending " and Seperate out value to array list
                    strElem.Remove(strElem.Length - 1, 1);
                    strTemp = strElem.ToString();
                    alParsedCsv.Add(strTemp);
                    strElem = new StringBuilder();
                    intCurrState = 5;
                    break;
                case 8:
                    //wipe ending " and Seperate out value to array list
                    strElem.Remove(strElem.Length - 1, 1);
                    strTemp = strElem.ToString();
                    alParsedCsv.Add(strTemp);
                    strElem = new StringBuilder();
                    //goto state 0
                    intCurrState = 0;
                    break;
            }
        }
    }
}
